<?php
namespace ff\network;

use ff;
use ff\base\Controller;
use ff\base\Exception;
use ReflectionMethod;

class Router
{
    private $routerKeys = ['_VERSION', '_CONTROLLER', '_ACTION', '_FORMAT'];
    private $_PATHVERSION;
    private $envVars = [];
    public $request;
    public $response;
    private $requestClass;
    private $responseClass;

    public function __construct($requestClass, $responseClass)
    {
        foreach ($this->routerKeys as $routerKey) {
            if (isset($_GET[$routerKey])) {
                $this->$routerKey = $_GET[$routerKey];
            }

        }
        if ($this->_VERSION) {
            $this->_PATHVERSION = str_replace('.', '_', $this->_VERSION);
        }

        $this->requestClass = $requestClass;
        $this->responseClass = $responseClass;

        $this->request = ff::createObject($requestClass);
        $this->response = ff::createObject($responseClass, [$this]);

    }

    public function response($outVars)
    {
        ($this->response)($outVars);
    }

    public function __set($name, $value)
    {
        if (!in_array($name, $this->routerKeys)) {
            throw new Exception('Setting unknown property: ' . get_class($this) . '::' . $name, 0);
        }
        $this->envVars[$name] = $value;
    }

    public function __get($name)
    {
        if (!in_array($name, $this->routerKeys)) {
            throw new Exception('Getting unknown property: ' . get_class($this) . '::' . $name, 0);
        }
        return $this->envVars[$name] ?? null;
    }

    public function runController()
    {

        $runControllerName = "controllers\\" . ($this->_VERSION ? $this->_PATHVERSION . '\\' : '') . $this->_CONTROLLER . 'Controller';

        if (!class_exists($runControllerName)) {
            return $this->response(['code' => -1001]);
        }

        $runController = ff::createObject($runControllerName, [$this->request]);
        $runAction = 'action' . ($this->_ACTION ?: $runController->defaultAction);

        $actionReflection = new ReflectionMethod($runControllerName, $runAction);
        if ($actionReflection->getNumberOfParameters() > 0) {
            $actionReflectionParameters = $actionReflection->getParameters();

            $actionParamkeys = array();
            foreach ($actionReflectionParameters as $key => $value) {
                $varname = $value->name;
                $actionParamkeys[$varname] = $key;
            }

            if (isset($actionParamkeys['method'])) {
                $methodkey = $actionParamkeys['method'];
                if ($actionReflectionParameters[$methodkey]->isOptional()) {
                    $methods = explode('|', $actionReflectionParameters[$methodkey]->getDefaultValue());
                    if (!in_array($this->request->method, $methods)) {
                        return $this->response(['code' => -1002]);
                    }
                } else {
                    throw new Exception($runControllerName . '::' . $runAction . ': The $method parameter must set the default value.', 0);
                }
            }

            if (isset($actionParamkeys['auth'])) {
                $authkey = $actionParamkeys['auth'];
                if ($actionReflectionParameters[$authkey]->isOptional()) {
                    $auths = explode('|', $actionReflectionParameters[$authkey]->getDefaultValue());

                    foreach ($auths as $authClassName) {
                        $FreeCallController = ff::createObject("ff\\auth\\" . $authClassName . 'AuthController', [$this->request]);
                        $FreeCallResult = $FreeCallController->auth($runController);

                        if (!is_null($FreeCallResult)) {
                            return $this->response($FreeCallResult);
                        }
                    }
                } else {
                    throw new Exception($runControllerName . '::' . $runAction . ': The $auth parameter must set the default value.', 0);
                }
            }
        }

        if (!method_exists($runController, $runAction) || !$runController instanceof Controller) {
            return $this->response(['code' => -1001]);
        }

        $classCallFunctions = array();
        $classCallFunctions[] = 'beforeAction';
        $classCallFunctions[] = $runAction;
        $classCallFunctions[] = 'afterAction';

        foreach ($classCallFunctions as $callfunction) {
            $return = $runController->$callfunction();
            if ($return !== null) {
                break;
            }
        }
        return $this->response($return);

    }
}
